package server;

import Events.BaseEvent;
import shared.Consts;
import Events.EventFactory;
import Events.EventPopulator;
import shared.PlayerPortfolio;
import server.ProfessionFactory.profession_type;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import network.NetQProxy;
import packet.ChatCmd;
import packet.CommandRequest;
import packet.InitGameCmd;
import packet.NextTurnCmd;
import packet.PlayerJoinCmd;
import packet.PlayerQuitCmd;
import packet.Request;

/**
 * Holds all data required for a game instance
 * @author Joel Garboden
 */
public class Game
{
  boolean gameInProgress;
  Game gameAccessor;
  PriorityBlockingQueue<CommandRequest> gameQ;

  public ArrayList<PlayerNode> playerList;

  Iterator<PlayerNode> activePlayerIt;
  PlayerNode activePlayer;

  EventPopulator eventPopulator;

  ProfessionFactory professionHandler;

  /**
   * Constructor
   * @param gameQ command queue
   */
  public Game(PriorityBlockingQueue<CommandRequest> gameQ)
  {
    eventPopulator = new EventPopulator();

    professionHandler = new ProfessionFactory();

    gameInProgress = false;
    this.gameQ = gameQ;

    gameAccessor = this;

    playerList = new ArrayList<>(0);
  }

  /**
   * Accessor
   * @return number of players
   */
  public int numPlayers()
  {
    return playerList.size();
  }

  /**
   * Broadcasts chat to all clients
   * @param playerName senders name
   * @param message outgoing chat message
   */
  public void broadcastChat(String playerName, String message)
  {
    ChatCmd cmd = new ChatCmd(playerName, message);
    for(PlayerNode p: playerList)
    {
      p.send(cmd);
    }
  }

  /**
   * Sends an updates portfolio to specified client
   * @param playerID players's client ID
   * @param portfolio updated player portfolio
   */
  public void playerUpdateCmd(int playerID, PlayerPortfolio portfolio)
  {
    playerList.get(playerID).send(portfolio);
  }

  /**
   * Handles players quitting
   * @param playerName name of player who's quitting
   * @throws IndexOutOfBoundsException when player isn't found
   */
  public void playerQuitCmd(String playerName) throws IndexOutOfBoundsException
  {
    for (int i = 0; i < playerList.size(); ++i)
    {
      if(playerList.get(i).getName() != null)
      {
        if (playerList.get(i).getName().equals(playerName))
        {
          PlayerQuitCmd cmd = new PlayerQuitCmd();
          playerList.get(i).send(cmd);
          broadcastChat(Consts.GM_NAME, playerList.get(i).getName()
                                        + " " + Consts.QUIT_MSG);
          playerList.get(i).setActive(false);
          return;
        }
      }
    }
  }

  /**
   *
   * @param proxy
   */
  public void addPlayer(NetQProxy proxy)
  {
    profession_type profID = ProfessionFactory.getRandomProfessionID();

    PlayerPortfolio portfolio = professionHandler.makePortfolioFor(profID, "Unknown");

    ArrayList<BaseEvent> eventList = eventPopulator.makeEventListFor(profID);

    EventFactory eventFactory = new EventFactory(eventList, new Random());

    PlayerNode newPlayer = new PlayerNode(playerList.size(), proxy, portfolio, eventFactory);

    playerList.add(newPlayer);

    newPlayer.send(new PlayerJoinCmd(playerList.size() - 1));

    if(playerList.size() >= Consts.MINIMUM_PLAYERS)
    {
      activePlayerIt = playerList.listIterator();
      activePlayer = (PlayerNode)activePlayerIt.next();
      activePlayer.send(new NextTurnCmd(true));
      broadcastChat(Consts.GM_NAME, activePlayer.getName() + " " + Consts.FIRST_TURN_MSG);
    }
  }

  /**
   *
   * @param playerID
   * @param playerName
   */
  public void updateName(int playerID, String playerName)
  {
    PlayerNode player = playerList.get(playerID);

    player.setName(playerName);

    player.sendPortfolio(); //send most up-to-date portfolio

    //For reconnecting only?
    broadcastChat(Consts.GM_NAME, playerName + " " + Consts.GAME_JOIN_MSG);
//    for(PlayerNode aPlayer: playerList)
//    {
//      if(aPlayer.getName() != null)
//      {
//        if(aPlayer.getName().equals(playerName))
//        {
//          player.setProxy(aPlayer.getProxy());
//          player.setID(playerID);
//          player.getProxy().queue = aPlayer.getProxy().queue;
//          player.setPortfolio(aPlayer.getPortfolio());
//          broadcastChat(player.getName(), Consts.RECONN_MSG);
//          player.send(new PortfolioUpdateCmd(player.getPortfolio()));
//          return;
//        }
//      }
//    }

    //Name wasn't found and not active game
    if(gameInProgress)
    {
      broadcastChat(player.getName(), Consts.KICK_MSG);
      player.send(new PlayerQuitCmd());
    }
    else
    {
      System.err.println("Game not in progress, but player not reconnecting.");
    }
  }

  /**
   * Advances the active player turn
   */
  public void nextTurn()
  {
    //Send current player data to make sure it's synced after user choices
    activePlayer.sendPortfolio();
    
    //Disable turn button
    activePlayer.send(new NextTurnCmd(false));

    broadcastChat(Consts.SYSTEM_NAME, activePlayer.getName() + " " + Consts.COMPLETED_TURN_MSG);

    try
    {
      activePlayer = activePlayerIt.next();
      while(activePlayer.isActive() != true)
      {
        activePlayer = activePlayerIt.next();
      }
    }
    catch(NoSuchElementException e)
    {
      activePlayerIt = playerList.listIterator();
      activePlayer = (PlayerNode)activePlayerIt.next();
    }


    activePlayer.nextWeek(gameQ, gameAccessor);

    //Update after weekly processing
    activePlayer.sendPortfolio();
    
    //Enable next turn after data update
    activePlayer.send(new NextTurnCmd(true));

    broadcastChat(Consts.SYSTEM_NAME, activePlayer.getName() + " " + Consts.STARTED_TURN_MSG);
  }

  /**
   * Initializes game for start
   */
  public void initGame(String playerName)
  {
    broadcastChat(Consts.GM_NAME, playerName + " " + Consts.STARTED_GAME_MSG);

    for(PlayerNode p: playerList)
    {
      InitGameCmd cmd = new InitGameCmd(p.getPortfolio());
      p.send(cmd);
    }

    activePlayerIt = playerList.listIterator();
    activePlayer = (PlayerNode)activePlayerIt.next();

    activePlayer.send(new NextTurnCmd(true));

    gameInProgress = true;
  }

  /**
   * Starts a LobbyListener to handle incoming packets
   */
  public void startListener()
  {
    new GameListener().start();
  }

  /**
   * LobbyListener for handling incoming packets
   * @author Joel Garboden
   */
  public class GameListener extends Thread
  {
    boolean listen = true;

    /**
     * Attempts to halt the listening thread as quickly as possible
     */
    public void stopNow()
    {
      try
      {
        listen = false;
        this.interrupt();
      }
      catch (Throwable ex)
      {
        Logger.getLogger(GameListener.class.getName()).log(Level.SEVERE, "GameListener: stopNow()", ex);
      }
    }

    /**
     * Continually listens on incoming network stream and <br/>
     * drops packets to queue for processing
     */
    @Override
    public void run()
    {
      while(listen)
      {
        try
        {
          Request req;
          System.out.println("Trying to take from gameQ");
          req = (Request)gameQ.take();
          System.out.println("Received: " + req.toString());

          req.processRequest(gameAccessor);
        }
        catch (Exception ex)
        {
          Logger.getLogger(GameListener.class.getName()).log(Level.SEVERE, "GameListener", ex);
        }
      }
    }
  }

}
